package org.hackystat.sensor.vim;

import java.io.IOException;
import org.hackystat.sensorshell.SensorShell;
import org.hackystat.sensorshell.SensorShellException;
import org.hackystat.sensorshell.SensorShellProperties;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.lang.StringBuffer;
import java.util.HashMap;
import java.util.Map;

/**
 * Provides sensor for VIM to collects editing activities.
 * 
 * updated for version 8 July 30, 2007
 * 
 * @author Dan Port
 */
public class HSVimSensor {

  /** Sample interval. */
  public static int SAMPLE_INTERVAL;

  /** Staleness tolerance. */
  public static int STALENESS_TOLERANCE;

  private File vimDataFile;

  private File currVimFile;

  private SensorShell theSensorShell;

  private SensorShellProperties theProperties;

  private boolean isSilentMode = false;

  private PrintStream outStream = null;

  /**
   * Creates a new HSVimSensor.
   */
  public HSVimSensor() {
    currVimFile = null;
  }

  /**
   * Command line invocation.
   * 
   * @param args Arguments.
   */
  public static void main(String args[]) throws SensorShellException {
    HSVimSensor theSensor = new HSVimSensor();
    // Probably should use more general args[] parsing here
    if (args.length > 0 && args[0].equals("-silent")) {
      theSensor.isSilentMode = true;
    }
    theSensor.initSensor();
    theSensor.senseVimData();
  }

  /**
   * Initializes VIM sensor.
   * 
   */
  private void initSensor() throws SensorShellException {
    // Are we running silent?
    if (isSilentMode) {
      // try{
      // outStream = new PrintStream(new FileOutputStream("/dev/null"));
      outStream = new PrintStream(new PipedOutputStream());
      System.out.println("running silent");
      // } catch (FileNotFoundException fe) {}
    }
    else {
      outStream = System.out;
    }

    try {
      theProperties = new SensorShellProperties();
      theSensorShell = new SensorShell(theProperties, false, "Vim");
    }
    catch (SensorShellException e) {
      System.out.println("Error initializing SensorShellProperties or starting SensorShell.");
      e.printStackTrace();
    }

    // outStream.println(theProperties.getSensorPropertiesDir());

    // Get the path to the sensorshell jar file
    String sensorshellHomePath = theProperties.getProperty("HACKYSTAT_SENSORSHELL_HOME");
    if (sensorshellHomePath == null) {
      outStream
          .println("Error: HACKYSTAT_SENSORSHELL_HOME property must be set in sensors.properties");
      theSensorShell.quit();
      System.exit(1);
    }

    sensorshellHomePath = this.convertTildeToHomePath(sensorshellHomePath);

    // Set properties from sensor.properties
    // The sample interval in milliseconds, STATE_CHANGE_INTERVAL in seconds
    SAMPLE_INTERVAL = theProperties.getStateChangeInterval() * 1000;

    // get the path to the Vim sensors data file taht is generated by the
    // Vim sensor plugin will be stored. Defaults to ~/.hackystat/vim/HS_VIM_DATA.dat

    String HSDataPath = theProperties.getProperty("HACKYSTAT_VIM_SENSOR_DATA_FILE");
    if (HSDataPath == null)
      HSDataPath = "~/.hackystat/vim/HS_VIM_DATA.dat";
    HSDataPath = this.convertTildeToHomePath(HSDataPath);
    vimDataFile = new File(HSDataPath);
    try {
      String sourcePath = vimDataFile.getCanonicalPath();
      outStream.println("abs vim data path = " + sourcePath);
    }
    catch (IOException e) {
      // Invalid path
      e.printStackTrace(outStream);
    }

    // How many sample intervals to go before removing the HS Vim data file
    // and exit (defaults to 5)
    //String stalenessProp = theProperties.getProperty("HACKYSTAT_VIM_SENSOR_STALENESS_TOLERANCE");
    //int STALENESS_TOLERANCE = (stalenessProp != null) ? Integer.parseInt(stalenessProp) : 5;

    // All properties set, so get inital Vim file data to start sensor
    // We only need this now to get the current file being edited to
    // set the Resource value in the sensorshell.
    this.readCurrentVimDataFile();

    // Tell server about this sensor.
    // To make a SensorData instance, construct a Map with the desired
    // key-value pairs.
    // We will let the SensorShell generate the timestamp.
    Map<String, String> keyValMap = new HashMap<String, String>();
    keyValMap.put("Tool", "Vim");
    keyValMap.put("SensorDataType", "DevEvent");
    keyValMap.put("Resource", currVimFile.toURI().toString());
    keyValMap.put("Type", "Edit");

    // Now invoke add, which generates a SensorData instance using the
    // data in the
    // Map.
    try {
      theSensorShell.add(keyValMap);
      // sensor has been added to sensorbase
      outStream.println("Hackystat Vim Sensor Enabled");
    }
    catch (Exception e) {
      e.printStackTrace(outStream);
    }
  }

  /**
   * Collects VIM editing data.
   * 
   */
  private void senseVimData() throws SensorShellException {
    Map<String, String> currVimData = this.getVimHSData();
    if (currVimData != null) {
      // Invoke statechange, which adds to the SensorData instance using
      // the data in the Map returned from getVimHSData.
      try {
        // not sure converting what was a Long to an Int is a good idea here, but ...
        int checksum = Integer.parseInt(currVimData.get("ResourceChecksum"));
        theSensorShell.statechange(checksum, currVimData);
      }
      catch (Exception e) {
        e.printStackTrace(outStream);
      }
      /*
       * outStream.println("changeArgs:"+Arrays.asList(changeArgs));
       */
    }
    try {
      // re-sample Vim data as per StateChangeInterval (but must
      // convert to milliseconds)
      Thread.sleep(SAMPLE_INTERVAL);
    }
    catch (InterruptedException te) {
      te.printStackTrace();
    }
    this.senseVimData();
  }

  /**
   * Gets data from HS Vim data file written by Vim sensor.
   * 
   * @return Sensor data.
   */
  private Map<String, String> getVimHSData() throws SensorShellException {
    long currentBufferSize = this.readCurrentVimDataFile();

    outStream.println("currVimFile=" + currVimFile.toURI().toString());
    outStream.println("size=" + currentBufferSize);

    Map<String, String> senseData = null;
    // only send data when valid vim file exists
    if (currVimFile != null && currVimFile.exists()) {

      senseData = new HashMap<String, String>();
      senseData.put("Tool", "Vim");
      senseData.put("SensorDataType", "DevEvent");
      senseData.put("Resource", currVimFile.toURI().toString());
      senseData.put("Type", "Edit");
      senseData.put("Subtype", "StateChange");
      senseData.put("ResourceChecksum", Long.toString(currentBufferSize));
    }
    else {
      // read size from swap file becuase named file has not been written
      // yet?
      /* NOT YET IMPLEMENTED */
    }

    return senseData;
  }

  /**
   * Updates VIM data from Vim data file. It sets the current vim file name and returns the number
   * of bytes in the vim buffer for that file
   * 
   * @return current Vim buffer size.
   */
  private long readCurrentVimDataFile() throws SensorShellException {
    currVimFile = null;
    if (vimDataFile != null && vimDataFile.exists()) {
      try {
        // open the vim data file written by the vim plugin
        outStream.println("\nVim data file opened");
        BufferedReader in = new BufferedReader(new FileReader(vimDataFile));
        String vimData = in.readLine().trim();
        in.close();

        /*
         * // parse the line of data into space separted tokens String[] result = vimData.split("
         * "); for (int x = 0; x < result.length; x++) { outStream.println(result[x]); }
         */
        String[] tmp;
        // get the current file path
        // <TBD> check for valid data
        tmp = vimData.split("file:");

        // A little odd, but this parsing does not assume the data is in
        // a particular
        // order inside the HS Vin Data file. The format is
        // <data key>:<value> <data key>:<value> <data key>:<value> ....
        String fileInfo = tmp[1].split(" ")[0];
        currVimFile = new File(fileInfo);

        // get the current buffer size
        // <TBD> check for valid data
        tmp = vimData.split("bytes:");
        String tmpStr = tmp[1].split(" ")[0].replaceAll(",", "");

        long currSizeInfo = Long.parseLong(tmpStr);
        /**
         * check timestamp for staleness in case of vim crash or irregular exit that left bogus HS
         * Vim data file
         */
        // get the current timestamp of the HS Vim data file (timestamp
        // comes from Vim
        // maybe should just use file mod time?)
        // <TBD> check for valid data
        tmp = vimData.split("timestamp:");
        long currTimeInfo = Long.parseLong(tmp[1].split(" ")[0]);

        this.checkHSVimDataFileStaleness(currTimeInfo);

        // send back current size
        return currSizeInfo;

      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
    // HS Vim data file no longer exists. Vim likely exited so exit.
    else {
      System.err.println("No HS Vim data file! Exiting HSVimSensor...");
      // Make sure to invoke quit when your tool exits so that any remaining data is
      // sent:
      theSensorShell.quit();
      System.exit(0);
    }
    // send back bogus size
    return -1;
  }

  /**
   * Checks that the timestamp is resonable (within some multiple of sample time) and removes the HS
   * Vim datafile and exits if not.
   * 
   * <TBD> timestamp staleness check not implemented yet! Does nothing now... Maybe this should just
   * compare the timestamp with the file mod time?
   * 
   * @param timestamp Time stamp.
   */
  private void checkHSVimDataFileStaleness(long timestamp) {

    outStream.println("HS Vim data file timestamp=" + timestamp);
  }

  /**
   * Takes a String path value and replaces the ~ with the users home directory (as set in the HOME
   * system env variable). If no ~\ is at the head, then the original path value is returned
   * unchanged.
   * 
   * @param path Path string.
   */
  private String convertTildeToHomePath(String path) {
    // convert ~ to HOME path (if used)
    if (path.charAt(0) == '~') {
      // get the HOME directory of this user from the system env variable
      String homdDir = System.getenv("HOME");
      StringBuffer bufferPath = new StringBuffer(path);
      bufferPath.replace(0, 1, homdDir);
      path = bufferPath.toString();
    }

    return path;
  }

}
